#!/usr/bin/env python
# -*- coding: utf-8 -*-
##############################################################################
# File: view_qt4.py
# Author: Jean-Marc VARENNE-PAQUET
# Description: This is an image viewer.
# Version: 1.1
# License: GNU GPL v3
##############################################################################



# Import
import os, sys, pyexiv2
from PyQt4 import QtCore, QtGui








##########################################################
# Classes
##########################################################


# The main window, displayed in full screen.
class MainWindow(QtGui.QGraphicsView):

	# Constructor
	def __init__(self, parameters):
		QtGui.QGraphicsView.__init__(self)
		self.setWindowTitle("X-Img-Viewer")

		# Set widget appearance
		self.setStyleSheet("QGraphicsView { border-style: none; }")
		self.setBackgroundBrush(QtCore.Qt.black)
		self.resize(QtGui.QApplication.desktop().width(), QtGui.QApplication.desktop().height())
		self.showFullScreen()

		# Set widget behavior
		self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)
		self.setMouseTracking(True)
		self.setViewportUpdateMode(QtGui.QGraphicsView.FullViewportUpdate)
		self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
		QtCore.QObject.connect(self, QtCore.SIGNAL("customContextMenuRequested (const QPoint&)"), self.displayContextMenu)

		# Initialization
		self.scroll_hand_drag_zoom_2x_mode = True
		self.left_mouse_button_pressed = False
		self.ctrl_key_pressed = False
		self.slide_show_is_running = False
		self.animation = QtGui.QMovie() # to run .gif files animation
		self.pictures_paths_list = []
		self.current_directory_path = ""
		self.current_file_path = ""
		self.current_picture_id = 0
		self.current_pixmap = QtGui.QPixmap()

		# Add scene
		self.scene = QtGui.QGraphicsScene()
		self.setScene(self.scene)

		# Load files list
		if len(parameters) > 1:
			self.loadPath(parameters[1])
			self.displayPicture()
		else: # no input argument, then ask the user to choose a directory.
			self.selectFolder()

		# For the slide shows transitions.
		self.timer_slide_show = QtCore.QTimer(self)
		self.timer_slide_show.setInterval(10) # cyclic timer interval : 0.01s
		self.connect(self.timer_slide_show, QtCore.SIGNAL("timeout()"), self.runSlideShow)
		self.pixmap_item_1 = QtGui.QGraphicsPixmapItem()
		self.pixmap_item_2 = QtGui.QGraphicsPixmapItem()
		self.transition_counter = 0

		# Create border widgets to show/hide when needed.
		self.left_border_window = LeftBorderImageFilesBrowser(self)
		self.left_border_window.setGeometry(0, 0, QtGui.QApplication.desktop().width() / 2, QtGui.QApplication.desktop().height() )
		self.top_border_window = TopBorderDirectorySelector(self)
		self.top_border_window.setGeometry(0, 0, QtGui.QApplication.desktop().width(), 30 )
		self.bottom_border_window = BottomBorderPreviousNextController(self)
		self.bottom_border_window.setGeometry(0, QtGui.QApplication.desktop().height() - 30, QtGui.QApplication.desktop().width(), 30 )
		self.right_border_window = RightExifTable(self)
		self.right_border_window.setGeometry(QtGui.QApplication.desktop().width() - 500, 0, 500, QtGui.QApplication.desktop().height() )

	# Check if the input file is an image or not by using its extension. It returns True or False.
	def isAnImage(self, file_path):
		result = False
		file_name = os.path.basename(file_path)
		if file_name.find(".") >= 0:
			file_extension = file_name.split(".")[-1].lower()
			result = file_extension in ["png", "jpg", "jpeg", "bmp", "gif", "tga", "tiff", "pcx", "svg"]
		return result

	# Create a list of pictures path by using the script parameter. This one can be a directory path or a file path.
	def loadPath(self, file_or_dir_path):

		# Initialization
		self.pictures_paths_list = []
		self.current_picture_id = 0
		self.current_file_path = ""

		# The script parameter is a file
		if os.path.isfile(file_or_dir_path):
			self.current_file_path = unicode(file_or_dir_path, "utf8")
			if not self.isAnImage(self.current_file_path):
				print "ERROR: " + self.current_file_path + " is not a valid image !"
				QtGui.QMessageBox.warning(self, 'Attention','Le fichier suivant n\'est pas une image valide : \n' + self.current_file_path)
				sys.exit(1)
			else:
				directory_path = os.path.dirname(self.current_file_path)
				self.current_directory_path = directory_path
				if directory_path == "":
					directory_path = "."
					self.current_file_path = "./" + self.current_file_path
				self.current_file_path = self.current_file_path.replace("//","/")
				files_list = os.listdir(directory_path)
				files_list.sort()
				self.pictures_paths_list = []
				for a_file in files_list:
					if self.isAnImage(a_file):
						self.pictures_paths_list.append(os.path.join(directory_path, a_file))
				self.current_picture_id = self.pictures_paths_list.index(self.current_file_path)

		# The script parameter is a directory
		elif os.path.isdir(file_or_dir_path):
			directory_path = unicode(file_or_dir_path, "utf8")
			self.current_directory_path = directory_path
			files_list = os.listdir(directory_path)
			files_list.sort()
			self.pictures_paths_list = []
			for a_file in files_list:
				if self.isAnImage(a_file):
					self.pictures_paths_list.append(os.path.join(directory_path, a_file))
			if self.pictures_paths_list != []:
				self.pictures_paths_list.sort()
				self.current_picture_id = 0
				self.current_file_path = self.pictures_paths_list[0]
			else:
				QtGui.QMessageBox.warning(self, 'Attention','Aucune image trouvee dans le repertoire ' + directory_path)

		# The input parameter does not exist
		else:
			print "ERROR: " + file_or_dir_path + " is not a valid path !"
			sys.exit(1)

		# Get index of the image given as parameter
		if self.current_file_path != "":
			self.current_picture_id = self.pictures_paths_list.index(self.current_file_path)







	##########################################################
	# Keyboard and mouse events reimplementation
	##########################################################

	def keyPressEvent(self, event):
		QtGui.QGraphicsView.keyPressEvent(self, event)
		my_key = event.key()
		if my_key == QtCore.Qt.Key_Escape:
			sys.exit(0)
		if my_key == QtCore.Qt.Key_Return \
		or my_key == QtCore.Qt.Key_Enter \
		or my_key == QtCore.Qt.Key_Space:
			if self.slide_show_is_running == False:
				self.startSlideShow()
			else:
				self.stopSlideShow()
		elif my_key == QtCore.Qt.Key_F5:
			self.displayPicture() # reload
		elif my_key == QtCore.Qt.Key_F11:
			if self.isFullScreen() == True:
				self.showNormal()
			else:
				self.showFullScreen()
		elif my_key == QtCore.Qt.Key_Plus:
			self.zoomIncrease()
		elif my_key == QtCore.Qt.Key_Minus:
			self.zoomDecrease()
		elif my_key == QtCore.Qt.Key_Left \
		or my_key == QtCore.Qt.Key_Up:
			self.displayPreviousPicture()
		elif my_key == QtCore.Qt.Key_Right \
		or my_key == QtCore.Qt.Key_Down:
			self.displayNextPicture()
		elif my_key == QtCore.Qt.Key_PageUp:
			self.displayPreviousPicture()
		elif my_key == QtCore.Qt.Key_PageDown:
			self.displayNextPicture()
		elif my_key == QtCore.Qt.Key_Home:
			self.displayFirstPicture()
		elif my_key == QtCore.Qt.Key_End:
			self.displayLastPicture()
		elif my_key == QtCore.Qt.Key_Delete:
			self.deleteImageFile()
		elif my_key == QtCore.Qt.Key_0:
			self.displayPicture()
		elif my_key == QtCore.Qt.Key_1:
			self.zoomNPercent(1)
		elif my_key == QtCore.Qt.Key_2:
			self.zoomNPercent(2)
		elif my_key == QtCore.Qt.Key_3:
			self.zoomNPercent(3)
		elif my_key == QtCore.Qt.Key_4:
			self.zoomNPercent(4)
		elif my_key == QtCore.Qt.Key_5:
			self.zoomNPercent(5)
		elif my_key == QtCore.Qt.Key_6:
			self.zoomNPercent(6)
		elif my_key == QtCore.Qt.Key_7:
			self.zoomNPercent(7)
		elif my_key == QtCore.Qt.Key_8:
			self.zoomNPercent(8)
		elif my_key == QtCore.Qt.Key_9:
			self.zoomNPercent(9)
		elif my_key == QtCore.Qt.Key_Control:
			self.ctrl_key_pressed = True
			self.setDragMode(QtGui.QGraphicsView.RubberBandDrag)
		else:
			pass

	def keyReleaseEvent(self, event):
		QtGui.QGraphicsView.keyReleaseEvent(self, event)
		my_key = event.key()
		if my_key == QtCore.Qt.Key_Control:
			self.ctrl_key_pressed = False
			self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)
		else:
			pass

	def wheelEvent(self, event):
		QtGui.QGraphicsView.wheelEvent(self, event)
		if event.delta() > 0:
			self.displayPreviousPicture()
		else:
			self.displayNextPicture()

	def mousePressEvent(self, event):
		QtGui.QGraphicsView.mousePressEvent(self, event)
		my_event = event.button()
		if my_event == QtCore.Qt.LeftButton \
		and self.ctrl_key_pressed == False \
		and self.scroll_hand_drag_zoom_2x_mode == True:
			self.centerOn(self.mapToScene(event.pos()))
			self.scale(2, 2)
			self.left_mouse_button_pressed = True
		elif my_event == QtCore.Qt.MidButton:
			sys.exit(0)
		else:
			pass

	def mouseReleaseEvent(self, event):
		QtGui.QGraphicsView.mouseReleaseEvent(self, event)
		if event.button() == QtCore.Qt.LeftButton \
		and self.left_mouse_button_pressed == True:
			self.scale(0.5, 0.5)
			self.left_mouse_button_pressed = False

		painter_path = self.scene.selectionArea() # get selected area with the "RubberBandDrag" mode
		if painter_path.isEmpty() == False:
			self.fitInView(painter_path.boundingRect(), QtCore.Qt.KeepAspectRatio) # zoom on the selected area
			empty_painter_path = QtGui.QPainterPath()
			self.scene.setSelectionArea(empty_painter_path) # unset the selected area

	def mouseMoveEvent(self, event):
		QtGui.QGraphicsView.mouseMoveEvent(self, event)
		my_pos = event.pos()
		
		# Left
		if my_pos.x() < 5 and self.pictures_paths_list != []:
			self.left_border_window.update()
			self.left_border_window.show()
		if my_pos.x() > 30 + QtGui.QApplication.desktop().width() / 2:
			self.left_border_window.hide()

		# Right
		if my_pos.x() > QtGui.QApplication.desktop().width() - 5 and self.pictures_paths_list != []:
			self.right_border_window.update()
			self.right_border_window.show()
		if my_pos.x() < QtGui.QApplication.desktop().width() - 500 and self.pictures_paths_list != []:
			self.right_border_window.hide()

		# Top
		if my_pos.y() < 5:
			self.top_border_window.update()
			self.top_border_window.show()
		if my_pos.y() > 50:
			self.top_border_window.hide()

		# Bottom
		if my_pos.y() > QtGui.QApplication.desktop().height() - 5:
			self.bottom_border_window.update()
			self.bottom_border_window.show()
		if my_pos.y() < QtGui.QApplication.desktop().height() - 15:
			self.bottom_border_window.hide()

	# If the picture is small enough to be fully displayed on the screen, a right click on it does a zoom.
	def resizeEvent(self, event):
		QtGui.QGraphicsView.resizeEvent(self, event)
		if event.size().height() >= event.oldSize().height() \
		and event.size().width() >= event.oldSize().width():
			self.scroll_hand_drag_zoom_2x_mode = True
		else:
			self.scroll_hand_drag_zoom_2x_mode = False

	# Display on the screen the image and the image properties in a green text on the top-left corner.
	def paintEvent(self, event):
		QtGui.QGraphicsView.paintEvent(self, event)
		if self.viewport() and not self.slide_show_is_running: # Not needed during a slide show.
			painter = QtGui.QPainter(self.viewport())
			pen = QtGui.QPen()
			pen.setColor(QtCore.Qt.green)
			painter.setPen(pen)
			painter.drawText(10,10, "zoom: "+ str(100*max([self.matrix().m11(), self.matrix().m12(), self.matrix().m21(), self.matrix().m22()])) + "%")







	##########################################################
	# Display methods
	##########################################################

	# Display a picture from the global list and resize it to fit the screen if the picture is too large.
	def displayPicture(self):
		# If the directory is empty, or if the slide show is running, do nothing.
		if len(self.pictures_paths_list) == 0 \
		or self.slide_show_is_running:
			return

		# Reset scene
		self.scene.clear()
		self.animation.stop()
		self.resetTransform()

		# Initialize image.
		self.current_file_path = self.pictures_paths_list[self.current_picture_id]
		self.current_pixmap.load(self.current_file_path)

		# Add image on the scene
		pixmap_item = self.scene.addPixmap(self.current_pixmap)
		self.centerOn(pixmap_item)
		self.scene.setSceneRect(pixmap_item.boundingRect())

		# Get EXIF information if any
		metadata_exif_keys = {}
		metadata = pyexiv2.ImageMetadata(self.current_file_path)
		try: # pyexiv2 cannot read metadata from a .svg file
			metadata.read()
			metadata_exif_keys = metadata.exif_keys
		except:
			pass
		if metadata_exif_keys != {}:
			# Take into account the picture orientation.
			try:
				orientation = int(metadata['Exif.Image.Orientation'].raw_value)
				if orientation == 3:
					self.rotate(180)
				elif orientation == 6:
					self.rotate(90)
				elif orientation == 8:
					self.rotate(-90)
				else:
					pass
			except:
				pass

		# Resize the scene to fit the screen if the image is too big to be completely displayed.
		if self.width() < self.current_pixmap.width() or self.height() < self.current_pixmap.height():
			self.fitInView(pixmap_item, QtCore.Qt.KeepAspectRatio)

		# Run .gif animation if this is a .gif file.
		if self.pictures_paths_list[self.current_picture_id].endswith(".gif"):
			self.animation.setFileName(self.pictures_paths_list[self.current_picture_id])
			QtCore.QObject.connect(self.animation, QtCore.SIGNAL("updated( const QRect & )"), lambda: pixmap_item.setPixmap(self.animation.currentPixmap()))
			self.animation.start()

	def displayNextPicture(self):
		if self.pictures_paths_list != [] and self.current_picture_id < len(self.pictures_paths_list) - 1:
			self.current_picture_id = self.current_picture_id + 1
			self.displayPicture()

	def displayPreviousPicture(self):
		if self.pictures_paths_list != [] and self.current_picture_id > 0:
			self.current_picture_id = self.current_picture_id - 1
			self.displayPicture()

	def displayFirstPicture(self):
		if self.pictures_paths_list != []:
			self.current_picture_id = 0
			self.displayPicture()

	def displayLastPicture(self):
		if self.pictures_paths_list != []:
			self.current_picture_id = len(self.pictures_paths_list) - 1
			self.displayPicture()

	def displaySelectedPicture(self, model_index):
		self.current_picture_id = model_index.row()
		self.displayPicture()




	##########################################################
	# Zoom methods
	##########################################################

	def zoomNPercent(self, n):
		self.resetMatrix()
		self.scale(n,n)

	def zoomIncrease(self):
		self.scale(1.25,1.25)

	def zoomDecrease(self):
		self.scale(0.75,0.75)




	##########################################################
	# Picture edition methods
	##########################################################

	def monochrome(self):
		# Modify image
		image = self.current_pixmap.toImage()
		image = image.convertToFormat(QtGui.QImage.Format_Mono, QtCore.Qt.ThresholdDither)
		self.current_pixmap = self.current_pixmap.fromImage(image)

		# Reset scene
		self.scene.clear()
		self.animation.stop()

		# Add image on the scene
		pixmap_item = self.scene.addPixmap(self.current_pixmap)
		self.centerOn(pixmap_item)
		self.scene.setSceneRect(pixmap_item.boundingRect())

	def invertColors(self):
		# Modify image
		image = self.current_pixmap.toImage()
		image.invertPixels()
		self.current_pixmap = self.current_pixmap.fromImage(image)

		# Reset scene
		self.scene.clear()
		self.animation.stop()

		# Add image on the scene
		pixmap_item = self.scene.addPixmap(self.current_pixmap)
		self.centerOn(pixmap_item)
		self.scene.setSceneRect(pixmap_item.boundingRect())




	##########################################################
	# Slide show management
	##########################################################

	def startSlideShow(self):
		self.transition_counter = 0
		self.slide_show_is_running = True
		self.timer_slide_show.start()

	def stopSlideShow(self):
		self.slide_show_is_running = False
		self.timer_slide_show.stop()

	# This method is called by a cyclic timer.
	def runSlideShow(self):

		# Exit the slide show if there is less than two images to display.
		if self.current_picture_id > len(self.pictures_paths_list) - 2:
			self.stopSlideShow()
			QtGui.QMessageBox.information(self, " ","Fin du diaporama.") # Show a popup.
			return

		# Reinitialize the scene.
		if self.transition_counter == 0:

			# Reset scene
			self.scene.clear()
			self.animation.stop()
			self.resetTransform()

			# Add image 1
			file_path_1 = self.pictures_paths_list[self.current_picture_id]
			pixmap_1 = QtGui.QPixmap()
			pixmap_1.load(file_path_1)
			self.pixmap_item_1 = self.scene.addPixmap(pixmap_1)

			# Add image 2
			file_path_2 = self.pictures_paths_list[self.current_picture_id+1]
			pixmap_2 = QtGui.QPixmap()
			pixmap_2.load(file_path_2)
			self.pixmap_item_2 = self.scene.addPixmap(pixmap_2)

			self.current_file_path = self.pictures_paths_list[self.current_picture_id+1]

			# Center on image 2
			self.centerOn(self.pixmap_item_2)
			self.scene.setSceneRect(self.pixmap_item_2.boundingRect())

			# Get EXIF information if any
			metadata_exif_keys = {}
			metadata = pyexiv2.ImageMetadata(file_path_2)
			try: # pyexiv2 cannot read metadata from a .svg file
				metadata.read()
				metadata_exif_keys = metadata.exif_keys
			except:
				pass
			if metadata_exif_keys != {}:
				# Take into account the picture orientation.
				try:
					orientation = int(metadata['Exif.Image.Orientation'].raw_value)
					if orientation == 3:
						self.rotate(180)
					elif orientation == 6:
						self.rotate(90)
					elif orientation == 8:
						self.rotate(-90)
					else:
						pass
				except:
					pass

			# Resize the scene to fit the screen.
			self.fitInView(self.pixmap_item_2, QtCore.Qt.KeepAspectRatio)

		# Modify images transparency during 0.20s
		self.pixmap_item_1.setOpacity(1.0 - self.transition_counter * 0.05)
		self.pixmap_item_2.setOpacity(0.0 + self.transition_counter * 0.05)

		# Increment the counter.
		self.transition_counter = self.transition_counter + 1

		# Time between two images : 3s
		if self.transition_counter > 300:
			self.current_picture_id = self.current_picture_id + 1
			self.transition_counter = 0





	##########################################################
	# Context menu displayed on right click
	##########################################################

	def displayContextMenu(self, point):
		menu = QtGui.QMenu("Menu", self)

		# Open
		action_open = QtGui.QAction(self)
		action_open.setText("Ouvrir")
		action_open.setShortcut(QtGui.QKeySequence("Ctrl+O"))
		action_open.setIcon(QtGui.QIcon.fromTheme("document-open-folder"))
		QtCore.QObject.connect(action_open, QtCore.SIGNAL("triggered()"), self.selectFolder)
		menu.addAction(action_open)

		# Edit
		menu_edit = QtGui.QMenu("Editer", self)
		menu_edit.setIcon(QtGui.QIcon.fromTheme("document-edit"))
		action_monochrome = QtGui.QAction(self)
		action_monochrome.setText("Monochrome")
		action_monochrome.setIcon(QtGui.QIcon.fromTheme("layer-visible-off"))
		QtCore.QObject.connect(action_monochrome, QtCore.SIGNAL("triggered()"), self.monochrome)
		menu_edit.addAction(action_monochrome)
		action_invert_colors = QtGui.QAction(self)
		action_invert_colors.setText("Inversion des couleurs")
		action_invert_colors.setIcon(QtGui.QIcon.fromTheme("layer-visible-off"))
		QtCore.QObject.connect(action_invert_colors, QtCore.SIGNAL("triggered()"), self.invertColors)
		menu_edit.addAction(action_invert_colors)
		action_edit_with_gimp = QtGui.QAction(self)
		action_edit_with_gimp.setText("Editer avec Gimp")
		action_edit_with_gimp.setIcon(QtGui.QIcon.fromTheme("gimp"))
		QtCore.QObject.connect(action_edit_with_gimp, QtCore.SIGNAL("triggered()"), lambda : os.system("gimp \"" + self.current_file_path + "\" &"))
		menu_edit.addAction(action_edit_with_gimp)
		
		menu.addMenu(menu_edit)

		# Save as
		action_save_as = QtGui.QAction(self)
		action_save_as.setText("Enregistrer sous...")
		action_save_as.setShortcut(QtGui.QKeySequence("Ctrl+S"))
		action_save_as.setIcon(QtGui.QIcon.fromTheme("document-save-as"))
		QtCore.QObject.connect(action_save_as, QtCore.SIGNAL("triggered()"), self.savePictureAs)
		menu.addAction(action_save_as)

		# Reload
		action_reload = QtGui.QAction(self)
		action_reload.setText("Recharger")
		action_reload.setShortcut(QtGui.QKeySequence("Ctrl+R"))
		action_reload.setIcon(QtGui.QIcon.fromTheme("view-refresh"))
		QtCore.QObject.connect(action_reload, QtCore.SIGNAL("triggered()"), self.displayPicture)
		menu.addAction(action_reload)

		# Delete
		action_delete = QtGui.QAction(self)
		action_delete.setText("Supprimer")
		action_delete.setShortcut(QtGui.QKeySequence("Del"))
		action_delete.setIcon(QtGui.QIcon.fromTheme("edit-delete"))
		QtCore.QObject.connect(action_delete, QtCore.SIGNAL("triggered()"), self.deleteImageFile)
		menu.addAction(action_delete)

		menu.addSeparator()

		# Zoom
		menu_zoom = QtGui.QMenu("Zoom", self)
		menu_zoom.setIcon(QtGui.QIcon.fromTheme("zoom-original"))
		action_zoomIncrease = QtGui.QAction(self)
		action_zoomIncrease.setText("Zoom +")
		action_zoomIncrease.setShortcut(QtGui.QKeySequence("+"))
		action_zoomIncrease.setIcon(QtGui.QIcon.fromTheme("zoom-in"))
		QtCore.QObject.connect(action_zoomIncrease, QtCore.SIGNAL("triggered()"), self.zoomIncrease)
		menu_zoom.addAction(action_zoomIncrease)
		action_zoom_less = QtGui.QAction(self)
		action_zoom_less.setText("Zoom -")
		action_zoom_less.setShortcut(QtGui.QKeySequence("-"))
		action_zoom_less.setIcon(QtGui.QIcon.fromTheme("zoom-out"))
		QtCore.QObject.connect(action_zoom_less, QtCore.SIGNAL("triggered()"), self.zoomDecrease)
		menu_zoom.addAction(action_zoom_less)
		menu_zoom.addSeparator()
		action_fit_in_view = QtGui.QAction(self)
		action_fit_in_view.setText("Ajuster a la fenetre")
		action_fit_in_view.setShortcut(QtGui.QKeySequence("0"))
		QtCore.QObject.connect(action_fit_in_view, QtCore.SIGNAL("triggered()"), lambda: self.fitInView(self.scene.sceneRect(), QtCore.Qt.KeepAspectRatio))
		menu_zoom.addAction(action_fit_in_view)
		action_zoom_100 = QtGui.QAction(self)
		action_zoom_100.setText("Zoom 100%")
		action_zoom_100.setShortcut(QtGui.QKeySequence("1"))
		QtCore.QObject.connect(action_zoom_100, QtCore.SIGNAL("triggered()"), lambda: self.zoomNPercent(1))
		menu_zoom.addAction(action_zoom_100)
		action_zoom_200 = QtGui.QAction(self)
		action_zoom_200.setText("Zoom 200%")
		action_zoom_200.setShortcut(QtGui.QKeySequence("2"))
		QtCore.QObject.connect(action_zoom_200, QtCore.SIGNAL("triggered()"), lambda: self.zoomNPercent(2))
		menu_zoom.addAction(action_zoom_200)
		action_zoom_300 = QtGui.QAction(self)
		action_zoom_300.setText("Zoom 300%")
		action_zoom_300.setShortcut(QtGui.QKeySequence("3"))
		QtCore.QObject.connect(action_zoom_300, QtCore.SIGNAL("triggered()"), lambda: self.zoomNPercent(3))
		menu_zoom.addAction(action_zoom_300)
		action_zoom_400 = QtGui.QAction(self)
		action_zoom_400.setText("Zoom 400%")
		action_zoom_400.setShortcut(QtGui.QKeySequence("4"))
		QtCore.QObject.connect(action_zoom_400, QtCore.SIGNAL("triggered()"), lambda: self.zoomNPercent(4))
		menu_zoom.addAction(action_zoom_400)
		action_zoom_500 = QtGui.QAction(self)
		action_zoom_500.setText("Zoom 500%")
		action_zoom_500.setShortcut(QtGui.QKeySequence("5"))
		QtCore.QObject.connect(action_zoom_500, QtCore.SIGNAL("triggered()"), lambda: self.zoomNPercent(5))
		menu_zoom.addAction(action_zoom_500)
		action_zoom_600 = QtGui.QAction(self)
		action_zoom_600.setText("Zoom 600%")
		action_zoom_600.setShortcut(QtGui.QKeySequence("6"))
		QtCore.QObject.connect(action_zoom_600, QtCore.SIGNAL("triggered()"), lambda: self.zoomNPercent(6))
		menu_zoom.addAction(action_zoom_600)
		action_zoom_700 = QtGui.QAction(self)
		action_zoom_700.setText("Zoom 700%")
		action_zoom_700.setShortcut(QtGui.QKeySequence("7"))
		QtCore.QObject.connect(action_zoom_700, QtCore.SIGNAL("triggered()"), lambda: self.zoomNPercent(7))
		menu_zoom.addAction(action_zoom_700)
		action_zoom_800 = QtGui.QAction(self)
		action_zoom_800.setText("Zoom 800%")
		action_zoom_800.setShortcut(QtGui.QKeySequence("8"))
		QtCore.QObject.connect(action_zoom_800, QtCore.SIGNAL("triggered()"), lambda: self.zoomNPercent(8))
		menu_zoom.addAction(action_zoom_800)
		action_zoom_900 = QtGui.QAction(self)
		action_zoom_900.setText("Zoom 900%")
		action_zoom_900.setShortcut(QtGui.QKeySequence("9"))
		QtCore.QObject.connect(action_zoom_900, QtCore.SIGNAL("triggered()"), lambda: self.zoomNPercent(9))
		menu_zoom.addAction(action_zoom_900)
		menu.addMenu(menu_zoom)

		# Rotation
		menu_rotate = QtGui.QMenu("Rotation", self)
		menu_rotate.setIcon(QtGui.QIcon.fromTheme("transform-rotate"))
		action_rotate_left = QtGui.QAction(self)
		action_rotate_left.setText("Rotation de -90")
		action_rotate_left.setIcon(QtGui.QIcon.fromTheme("object-rotate-left"))
		QtCore.QObject.connect(action_rotate_left, QtCore.SIGNAL("triggered()"), lambda: self.rotate(-90))
		menu_rotate.addAction(action_rotate_left)
		action_rotate_right = QtGui.QAction(self)
		action_rotate_right.setText("Rotation de 90")
		action_rotate_right.setIcon(QtGui.QIcon.fromTheme("object-rotate-right"))
		QtCore.QObject.connect(action_rotate_right, QtCore.SIGNAL("triggered()"), lambda: self.rotate(90))
		menu_rotate.addAction(action_rotate_right)
		menu.addMenu(menu_rotate)

		# Flip
		menu_flip = QtGui.QMenu("Flip", self)
		menu_flip.setIcon(QtGui.QIcon.fromTheme("object-flip-horizontal"))
		action_horizontal_flip = QtGui.QAction(self)
		action_horizontal_flip.setText("Flip horizontal")
		action_horizontal_flip.setIcon(QtGui.QIcon.fromTheme("object-flip-horizontal"))
		QtCore.QObject.connect(action_horizontal_flip, QtCore.SIGNAL("triggered()"), lambda: self.scale(-1,1))
		menu_flip.addAction(action_horizontal_flip)
		action_vertical_flip = QtGui.QAction(self)
		action_vertical_flip.setText("Flip vertical")
		action_vertical_flip.setIcon(QtGui.QIcon.fromTheme("object-flip-vertical"))
		QtCore.QObject.connect(action_vertical_flip, QtCore.SIGNAL("triggered()"), lambda: self.scale(1,-1))
		menu_flip.addAction(action_vertical_flip)
		menu.addMenu(menu_flip)

		menu.addSeparator()

		# Browse
		menu_browse = QtGui.QMenu("Naviguer", self)
		menu_browse.setIcon(QtGui.QIcon.fromTheme("document-swap"))
		action_go_to_first = QtGui.QAction(self)
		action_go_to_first.setText("Premiere image")
		action_go_to_first.setShortcut(QtGui.QKeySequence("Home"))
		action_go_to_first.setIcon(QtGui.QIcon.fromTheme("arrow-left-double"))
		QtCore.QObject.connect(action_go_to_first, QtCore.SIGNAL("triggered()"), self.displayFirstPicture)
		menu_browse.addAction(action_go_to_first)
		action_previous = QtGui.QAction(self)
		action_previous.setText("Image precedente")
		action_previous.setShortcut(QtGui.QKeySequence("PgUp"))
		action_previous.setIcon(QtGui.QIcon.fromTheme("arrow-left"))
		QtCore.QObject.connect(action_previous, QtCore.SIGNAL("triggered()"), self.displayPreviousPicture)
		menu_browse.addAction(action_previous)
		action_next = QtGui.QAction(self)
		action_next.setText("Image suivante")
		action_next.setShortcut(QtGui.QKeySequence("PgDown"))
		action_next.setIcon(QtGui.QIcon.fromTheme("arrow-right"))
		QtCore.QObject.connect(action_next, QtCore.SIGNAL("triggered()"), self.displayNextPicture)
		menu_browse.addAction(action_next)
		action_go_to_last = QtGui.QAction(self)
		action_go_to_last.setText("Derniere image")
		action_go_to_last.setShortcut(QtGui.QKeySequence("End"))
		action_go_to_last.setIcon(QtGui.QIcon.fromTheme("arrow-right-double"))
		QtCore.QObject.connect(action_go_to_last, QtCore.SIGNAL("triggered()"), self.displayLastPicture)
		menu_browse.addAction(action_go_to_last)
		menu.addMenu(menu_browse)

		# Slide show
		action_slide_show = QtGui.QAction(self)
		if self.slide_show_is_running == False:
			action_slide_show.setText("Demarrer diaporama")
			action_slide_show.setIcon(QtGui.QIcon.fromTheme("window-duplicate"))
			QtCore.QObject.connect(action_slide_show, QtCore.SIGNAL("triggered()"), self.startSlideShow)
		else:
			action_slide_show.setText("Arreter diaporama")
			action_slide_show.setIcon(QtGui.QIcon.fromTheme("process-stop"))
			QtCore.QObject.connect(action_slide_show, QtCore.SIGNAL("triggered()"), self.stopSlideShow)
		menu.addAction(action_slide_show)

		menu.addSeparator()

		# About QT
		action_about_qt = QtGui.QAction(self)
		action_about_qt.setText("A propos de QT")
		action_about_qt.setIcon(QtGui.QIcon.fromTheme("help-about"))
		QtCore.QObject.connect(action_about_qt, QtCore.SIGNAL("triggered()"), self.displayAboutQTWindow)
		menu.addAction(action_about_qt)

		# About app
		action_about_app = QtGui.QAction(self)
		action_about_app.setText("A propos de cette application")
		action_about_app.setIcon(QtGui.QIcon.fromTheme("emoticon"))
		QtCore.QObject.connect(action_about_app, QtCore.SIGNAL("triggered()"), self.displayAboutAppWindow)
		menu.addAction(action_about_app)

		menu.addSeparator()

		# Quit
		action_quit = QtGui.QAction(self)
		action_quit.setText("Quitter")
		action_quit.setShortcut(QtGui.QKeySequence("Esc"))
		action_quit.setIcon(QtGui.QIcon.fromTheme("application-exit"))
		QtCore.QObject.connect(action_quit, QtCore.SIGNAL("triggered()"), lambda: sys.exit(0))
		menu.addAction(action_quit)

		menu.exec_(self.mapToGlobal(point)) 





	##########################################################
	# Subwindows opening methods
	##########################################################

	# Open a window to select a folder.
	def selectFolder(self):
		file_path = QtGui.QFileDialog().getExistingDirectory(self, 'Ouvrir un repertoire', self.current_directory_path)
		file_path = unicode(file_path).encode('utf-8')
		if file_path != "":
			self.current_directory_path = file_path
			self.loadPath(self.current_directory_path)
			self.displayPicture()

	# Open a window to save an image to the hard disk.
	def savePictureAs(self):
		file_path = os.path.basename(self.pictures_paths_list[self.current_picture_id]) # default file name
		file_path = QtGui.QFileDialog().getSaveFileName(self,'Enregistrer l\'image sous',file_path,'image file (*.png *.jpg *.jpeg *.bmp *.gif *.tga *.tiff *.pcx *.svg);;All files (*)','')
		if file_path != "":
			file_format = "" # The extension given to the new file name will be used to determine the format of this new file.
			try:
				file_format = os.path.basename(file_path).split(".")[1].upper()
			except:
				pass
			self.current_pixmap.save(file_path, file_format)

	# Open a confirmation window before deleting image file.
	def deleteImageFile(self):
		answer = QtGui.QMessageBox.warning(self, 'Attention','Etes-vous sur de vouloir supprimer ' + self.pictures_paths_list[self.current_picture_id] + ' ?', QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
		if answer == QtGui.QMessageBox.Yes:
			os.unlink(self.pictures_paths_list[self.current_picture_id])
			self.pictures_paths_list.remove(self.pictures_paths_list[self.current_picture_id])
			if len(self.pictures_paths_list) == 0 or self.current_picture_id == len(self.pictures_paths_list):
				self.scene.clear()
				self.animation.stop()
			else:
				self.displayPicture()

	# Open a window which displays information about the QT version.
	def displayAboutQTWindow(self):
		self.about_window = QtGui.QMessageBox()
		self.about_window.aboutQt(self, "A propos de QT")

	# Open a window which displays information about the script.
	def displayAboutAppWindow(self):
		self.about_window = QtGui.QMessageBox()
		self.about_window.about(self, "A propos", self.windowTitle() + "\nRealisation de Jean-Marc VARENNE-PAQUET\nVersion 1.1\nSous licence GNU GPL v3")















# Window that displays thumbnail previews. 
class LeftBorderImageFilesBrowser(QtGui.QListView):

	# Constructor
	def __init__(self, parent):
		QtGui.QListView.__init__(self, parent)
		self.parent = parent

		# Set list view options.
		self.setIconSize(QtCore.QSize(200,200))
		self.setUniformItemSizes(True)
		self.setViewMode(QtGui.QListView.IconMode)
		self.setMovement(QtGui.QListView.Static)
		QtCore.QObject.connect(self, QtCore.SIGNAL("doubleClicked (const QModelIndex &)"), self.close)
		if parent != None:
			QtCore.QObject.connect(self, QtCore.SIGNAL("clicked (const QModelIndex &)"), parent.displaySelectedPicture)

		# Set model
		self.model = QtGui.QStandardItemModel(self)
		self.setModel(self.model)

	def update(self):

		item_list = []
		for file_path in self.parent.pictures_paths_list:

			# Get thumbnail preview from the EXIF information.
			metadata_previews = []
			metadata_exif_keys = []
			pixmap = QtGui.QPixmap()
			metadata = pyexiv2.ImageMetadata(file_path)
			try: # Exiv2 is not compatible with the SVG format.
				metadata.read()
				metadata_previews = metadata.previews
				metadata_exif_keys = metadata.exif_keys
			except:
				pass
			for i, p in enumerate(metadata_previews):
				pixmap.loadFromData(p.data)

			# Get orientation from the EXIF information and apply it on the thumbnail preview.
			if 'Exif.Image.Orientation' in metadata_exif_keys:
				orientation = int(metadata['Exif.Image.Orientation'].raw_value)
				t = QtGui.QTransform()
				if orientation == 3:
					t.rotate(180)
				elif orientation == 6:
					t.rotate(90)
				elif orientation == 8:
					t.rotate(-90)
				else:
					pass
				pixmap = pixmap.transformed(t)

			# If there is no EXIF information, then build the thumbnail preview buy using direclty the original image.
			if pixmap.isNull():
				pixmap.load(file_path)

			# Create an item 
			item = QtGui.QStandardItem()
			item.setText(QtCore.QString(os.path.basename(file_path)))
			icon = QtGui.QIcon()
			icon.addPixmap(pixmap)
			item.setIcon(icon)
			item_list.append(item)

		# Update the model.
		self.model.clear()
		self.model.insertColumn(0, item_list)

		# Set current image as selected.
		model_index = self.model.index(self.parent.current_picture_id, 0)
		self.setCurrentIndex(model_index)
		self.scrollTo(model_index)
		self.setFocus()









# Tool bar that allows to open a directory.
class TopBorderDirectorySelector(QtGui.QToolBar):

	# Constructor
	def __init__(self, parent):
		QtGui.QToolBar.__init__(self, parent)
		self.parent = parent

		self.label_dir_info = QtGui.QLabel()
		self.label_dir_info.setSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
		self.addWidget(self.label_dir_info)

		# Select directory
		action_select_folder = QtGui.QAction(self)
		action_select_folder.setText("Ouvrir un repertoire")
		action_select_folder.setIcon(QtGui.QIcon.fromTheme("document-open-folder"))
		QtCore.QObject.connect(action_select_folder, QtCore.SIGNAL("triggered()"), self.selectFolder)
		self.addAction(action_select_folder)

		# Strech the toolbar here. In the future, it should be : self.tool_bar.addStretch(1)
		separator = QtGui.QWidget(self)
		separator.setSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
		self.addWidget(separator)

		# Quit
		action_quit = QtGui.QAction(self)
		action_quit.setText("Quitter")
		action_quit.setIcon(QtGui.QIcon.fromTheme("application-exit"))
		QtCore.QObject.connect(action_quit, QtCore.SIGNAL("triggered()"), lambda: sys.exit(0))
		self.addAction(action_quit)

		self.update()

	def update(self):
		self.label_dir_info.setText(self.parent.current_directory_path)
		self.setFocus()

	def selectFolder(self):
		self.parent.selectFolder()
		self.update()






# Tool bar that allows to go to select an image.
class BottomBorderPreviousNextController(QtGui.QToolBar):

	# Constructor
	def __init__(self, parent):
		QtGui.QToolBar.__init__(self, parent)
		self.parent = parent

		self.label_file_info = QtGui.QLabel()
		self.label_file_info.setSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
		self.addWidget(self.label_file_info)

		action_previous = QtGui.QAction(self)
		action_previous.setText("Image precedente")
		action_previous.setShortcut(QtGui.QKeySequence("PgUp"))
		action_previous.setIcon(QtGui.QIcon.fromTheme("arrow-left"))
		QtCore.QObject.connect(action_previous, QtCore.SIGNAL("triggered()"), self.displayPreviousPicture)
		self.addAction(action_previous)

		action_next = QtGui.QAction(self)
		action_next.setText("Image suivante")
		action_next.setShortcut(QtGui.QKeySequence("PgDown"))
		action_next.setIcon(QtGui.QIcon.fromTheme("arrow-right"))
		QtCore.QObject.connect(action_next, QtCore.SIGNAL("triggered()"), self.displayNextPicture)
		self.addAction(action_next)

		# Strech the toolbar here. In the future, it should be : self.tool_bar.addStretch(1)
		separator = QtGui.QWidget(self)
		separator.setSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
		self.addWidget(separator)

		# Start/stop slide show
		self.action_slide_show = QtGui.QAction(self)
		self.addAction(self.action_slide_show)

		self.update()

	def update(self):
		# Update the displayed picture information.
		pixmap = self.parent.current_pixmap
		if pixmap.isNull():
			return
		text = " [ " + str(self.parent.current_picture_id+1) + " / " + str(len(self.parent.pictures_paths_list)) + " ] " + \
		os.path.basename(self.parent.current_file_path) + " " \
		+ str(os.path.getsize(self.parent.current_file_path)/1000) + "Kb " \
		+ str(pixmap.height()) + "x" + str(pixmap.width())
		self.label_file_info.setText(text)

		# Update the start/stop slide show action.
		if self.parent.slide_show_is_running == False:
			self.action_slide_show.setText("Demarrer diaporama")
			self.action_slide_show.setIcon(QtGui.QIcon.fromTheme("window-duplicate"))
			QtCore.QObject.connect(self.action_slide_show, QtCore.SIGNAL("triggered()"), self.parent.startSlideShow)
		else:
			self.action_slide_show.setText("Arreter diaporama")
			self.action_slide_show.setIcon(QtGui.QIcon.fromTheme("process-stop"))
			QtCore.QObject.connect(self.action_slide_show, QtCore.SIGNAL("triggered()"), self.parent.stopSlideShow)

		self.setFocus()

	def displayPreviousPicture(self):
		self.parent.displayPreviousPicture()
		self.update()

	def displayNextPicture(self):
		self.parent.displayNextPicture()
		self.update()

	def wheelEvent(self, event):
		QtGui.QToolBar.wheelEvent(self, event)
		self.parent.wheelEvent(event)
		self.update()








# A table that displays the EXIF information.
class RightExifTable(QtGui.QTableView):

	# Constructor
	def __init__(self, parent):
		QtGui.QTableView.__init__(self, parent)
		self.resize(550, 800)
		self.parent = parent
		self.update()

	def update(self):

		# Get EXIF information from picture file
		metadata_exif_keys = {}
		metadata = pyexiv2.ImageMetadata(self.parent.current_file_path)
		try: # pyexiv2 cannot read metadata from a .svg file
			metadata.read()
			metadata_exif_keys = metadata.exif_keys
		except:
			pass

		# Display EXIF information
		if metadata_exif_keys != {}:
			item = []
			model = QtGui.QStandardItemModel(self)
			self.setModel(model)
			exif_keys_list = metadata.exif_keys
			if exif_keys_list != []:
				for i in range(len(exif_keys_list)):
					item.append(["",""])
					my_key = exif_keys_list[i]
					item[i][0] = QtGui.QStandardItem()
					item[i][0].setText(my_key)
					model.setItem(i, 0, item[i][0])
					item[i][1] = QtGui.QStandardItem()
					item[i][1].setText(str(metadata[my_key].raw_value))
					model.setItem(i, 1, item[i][1])
					self.setRowHeight(i, 20)
			else:
				item = QtGui.QStandardItem()
				item.setText(unicode("Aucune donnée EXIF dans\n", "utf8") + os.path.basename(self.parent.current_file_path))
				model.setItem(0, 0, item)

		# Display options
		self.horizontalHeader().hide()
		self.verticalHeader().hide()
		self.setColumnWidth(0,300)
		self.setColumnWidth(1,250)











##########################################################
# Main
##########################################################

if __name__ == "__main__":

	# Display the main window
	app = QtGui.QApplication(sys.argv)
	main_windows = MainWindow(sys.argv)
	main_windows.show()
	sys.exit(app.exec_())


